﻿using System;
using System.Threading;
using Hallenberg.Core;
using Hallenberg.SmartClient.Events;
using NUnit.Framework;

namespace Hallenberg.SmartClient.Test.Events
{
    [TestFixture]
    public class EventManagerTest
    {
        private WorkItemController m_WorkItem;

        [SetUp]
        public void Initialize()
        {
            m_WorkItem = new WorkItemController();
        }

        [Test]
        public void SmartClient_EventManager_Constructor_Fail()
        {
            ArgumentNullException exception = Assert.Throws<ArgumentNullException>(() => new EventManager(null));

            Assert.That(exception, Is.Not.Null);
            Assert.That(exception.ParamName, Is.EqualTo("services"));
        }

        [Test]
        public void SmartClient_EventManager_Register_Inline_EventHandler()
        {
            bool eventRaised = false;

            m_WorkItem.Events.Register(false, (object sender, EventArgs<int> args) =>
            {
                eventRaised = true;
            });

            m_WorkItem.Events.Raise(this, 10);

            Assert.IsTrue(eventRaised);
        }

        [Test]
        public void SmartClient_EventManager_Register_Inline_Async_EventHandler()
        {
            int currentThreadId = Thread.CurrentThread.ManagedThreadId;
            int eventThreadId = -1;
            AutoResetEvent waitForEventHandled = new AutoResetEvent(false);

            m_WorkItem.Events.Register(true, (object sender, EventArgs<int> args) =>
            {
                eventThreadId = Thread.CurrentThread.ManagedThreadId;
                waitForEventHandled.Set();
            });

            m_WorkItem.Events.Raise(this, 10);

            Assert.That(waitForEventHandled.WaitOne(TimeSpan.FromSeconds(10)), Is.True);
            Assert.That(currentThreadId, Is.Not.EqualTo(eventThreadId));
        }

        [Test]
        public void SmartClient_EventManager_Raise_Multiple_Async_Verify_GC_Not_Removed()
        {
            Thread mtaThread = new Thread(() =>
                {
                    int numberOfRaises = 50;
                    AutoResetEvent[] eventHandled = new AutoResetEvent[numberOfRaises];
                    for (int i = 0; i < numberOfRaises; i++)
                    {
                        eventHandled[i] = new AutoResetEvent(false);
                    }

                    m_WorkItem.Events.Register(true, (object sender, EventArgs<EventData> args) =>
                    {
                        int testInt = args.Value.EventValue;

                        eventHandled[testInt].Set();
                    });

                    for (int raiseIndex = 0; raiseIndex < numberOfRaises; raiseIndex++)
                    {
                        m_WorkItem.Events.Raise(this, new EventData() { EventValue = raiseIndex });
                    }

                    GC.Collect();

                    // If an exception is thrown before this sleep has timed out, the passed EventData has been removed
                    // from GC and thus made it impossible for the async event to access the data passed to it.

                    Assert.That(AutoResetEvent.WaitAll(eventHandled, TimeSpan.FromSeconds(10)), Is.True);
                });

            mtaThread.SetApartmentState(ApartmentState.MTA);
            mtaThread.Start();
            mtaThread.Join();
        }

        [Test]
        public void SmartClient_EventManager_Raise_Pass_Null_ParamOne()
        {
            ArgumentNullException exception = Assert.Throws<ArgumentNullException>(() => m_WorkItem.Events.Raise(null, 10));

            Assert.That(exception, Is.Not.Null);
            Assert.That(exception.ParamName, Is.EqualTo("sender"));
        }

        [Test]
        public void SmartClient_EventManager_Raise_Pass_Null_ParamTwo()
        {
            ArgumentNullException exception = Assert.Throws<ArgumentNullException>(() => m_WorkItem.Events.Raise<object>(this, null));
            
            Assert.That(exception, Is.Not.Null);
            Assert.That(exception.ParamName, Is.EqualTo("value"));
        }
    }
}
